/*******************************************
 * Copyright 2007 Aphrodite Framework for javaME .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * Email : owner@aphroditeme.dev.java.net
 * Contributors:
 *     Kent Elton - initial API and implementation
 *******************************************/

package com.aphrodite.image;

import javax.microedition.lcdui.Image;

/**
 * Image scaling routines
 * <p>
 * The efficiency of alpha processing and rgb data extraction is platform
 * dependent
 * </p>
 * 
 * @author kentelt
 * 
 */
public class ImageScaling {
	public static final Image doBilinearScale(Image image, int newWidth, int newHeight) {
		int w = image.getWidth();
		int h = image.getHeight();
		int[] data = doBilinearScale(ImageUtil.getImageRGB(image, w, h), w, h, newWidth, newHeight);
		return Image.createRGBImage(data, newWidth, newHeight, false);

	}

	/**
	 * Perform bilinear scaling of the image.This method removes transparency
	 * <p>
	 * Its a bit slower than other scaling methods used in this class
	 * </p>
	 * 
	 * @param image
	 * @param newWidth
	 * @param newHeight
	 * @return
	 */
	public static final int[] doBilinearScale(int[] rawInput, int orgWidth, int orgHeight, int newWidth, int newHeight) {
		// ############ tested by $t3p#3n on 29-july #################//
		int orgLength = orgWidth * orgHeight;
		int orgMax = orgLength - 1;

		// new int[orgLength];
		// nativeImage.getRGB(rawInput, 0, orgWidth, 0, 0, orgWidth, orgHeight);
		int newLength = newWidth * newHeight;
		int[] rawOutput = new int[newLength];
		int yd = (orgHeight / newHeight - 1) * orgWidth;
		int yr = orgHeight % newHeight;
		int xd = orgWidth / newWidth;
		int xr = orgWidth % newWidth;
		int outOffset = 0;
		int inOffset = 0;

		// Whole pile of non array variables for the loop.
		int pixelA, pixelB, pixelC, pixelD;
		int xo, yo;
		int weightA, weightB, weightC, weightD;
		int redA, redB, redC, redD;
		int greenA, greenB, greenC, greenD;
		int blueA, blueB, blueC, blueD;
		int red, green, blue;

		for (int y = newHeight, ye = 0; y > 0; y--) {
			for (int x = newWidth, xe = 0; x > 0; x--) {

				// Set source pixels.
				pixelA = inOffset;
				pixelB = pixelA + 1;
				pixelC = pixelA + orgWidth;
				pixelD = pixelC + 1;

				// Get pixel values from array for speed, avoiding overflow.
				pixelA = rawInput[pixelA];
				pixelB = pixelB > orgMax ? pixelA : rawInput[pixelB];
				pixelC = pixelC > orgMax ? pixelA : rawInput[pixelC];
				pixelD = pixelD > orgMax ? pixelB : rawInput[pixelD];

				// Calculate pixel weights from error values xe & ye.
				xo = (xe << 8) / newWidth;
				yo = (ye << 8) / newHeight;
				weightD = xo * yo;
				weightC = (yo << 8) - weightD;
				weightB = (xo << 8) - weightD;
				weightA = 0x10000 - weightB - weightC - weightD;

				// Isolate colour channels.
				redA = pixelA >> 16;
				redB = pixelB >> 16;
				redC = pixelC >> 16;
				redD = pixelD >> 16;
				greenA = pixelA & 0x00FF00;
				greenB = pixelB & 0x00FF00;
				greenC = pixelC & 0x00FF00;
				greenD = pixelD & 0x00FF00;
				blueA = pixelA & 0x0000FF;
				blueB = pixelB & 0x0000FF;
				blueC = pixelC & 0x0000FF;
				blueD = pixelD & 0x0000FF;

				// Calculate new pixels colour and mask.
				red = 0x00FF0000 & (redA * weightA + redB * weightB + redC * weightC + redD * weightD);
				green = 0xFF000000 & (greenA * weightA + greenB * weightB + greenC * weightC + greenD * weightD);
				blue = 0x00FF0000 & (blueA * weightA + blueB * weightB + blueC * weightC + blueD * weightD);

				// Store pixel in output buffer and increment offset.
				rawOutput[outOffset++] = red + (((green | blue) >> 16));

				// Increment input by x delta.
				inOffset += xd;

				// Correct if we have a roll over error.
				xe += xr;
				if (xe >= newWidth) {
					xe -= newWidth;
					inOffset++;
				}
			}

			// Increment input by y delta.
			inOffset += yd;

			// Correct if we have a roll over error.
			ye += yr;
			if (ye >= newHeight) {
				ye -= newHeight;
				inOffset += orgWidth;
			}
		}
		return rawOutput;
	}

	public static final Image doBresenhamScale(Image image, int newWidth, int newHeight, boolean processAlpha) {
		int w = image.getWidth();
		int h = image.getHeight();
		int data[] = ImageUtil.getImageRGB(image, w, h);
		data = doBresenhamScale(data, w, h, newWidth, newHeight, processAlpha);
		return Image.createRGBImage(data, newWidth, newHeight, processAlpha);
	}

	/**
	 * Use bresenham algorithm to scale an image
	 * 
	 * @see http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
	 * @param rawInput
	 *            image input
	 * @param width
	 *            source width
	 * @param height
	 *            source height
	 * @param newWidth
	 *            destination width
	 * @param newHeight
	 *            destination height
	 * @param processAlpha
	 *            flag to process transparency
	 * @return scaled image
	 * @throws NullPointerException
	 *             if rawInput is null
	 * @throws IllegalArgumentException
	 *             if newWidth/newHeight is <0
	 */
	public static final int[] doBresenhamScale(int[] rawInput, int width, int height, int newWidth, int newHeight,
			boolean processAlpha) {
		// ############ tested by $t3p#3n on 29-july #################/
		if (rawInput == null) {
			// return null;
			throw new NullPointerException();
		}
		if (newWidth < 0 || newHeight < 0)
			throw new IllegalArgumentException("Invalid values for newWidth/newHeight");
		int[] rawOutput = new int[newWidth * newHeight];
		// YD compensates for the x loop by subtracting the width back out
		int YD = (height / newHeight) * width - width;
		int YR = height % newHeight;
		int XD = width / newWidth;
		int XR = width % newWidth;
		int outOffset = 0;
		int inOffset = 0;

		for (int y = newHeight, YE = 0; y > 0; y--) {
			for (int x = newWidth, XE = 0; x > 0; x--) {
				rawOutput[outOffset++] = rawInput[inOffset];
				inOffset += XD;
				XE += XR;
				if (XE >= newWidth) {
					XE -= newWidth;
					inOffset++;
				}
			}
			inOffset += YD;
			YE += YR;
			if (YE >= newHeight) {
				YE -= newHeight;
				inOffset += width;
			}
		}
		return rawOutput;

	}

	/**
	 * Scales the image without filling gaps between single pixels.
	 * 
	 * @param factor
	 *            the zoom factor in percent, must be greater than 100
	 * @param width
	 *            the width of the target RGB
	 * @param height
	 *            the height of the target RGB
	 * @param sourceRgb
	 *            the source RGB data that has the original data
	 * @param targetRgb
	 *            the target array for storing the scaled image
	 */
	public static void doParticleScale(int factor, int width, int height, int[] sourceRgb, int[] targetRgb) {
		// TODO [aphrodite] test this routine
		for (int i = 0; i < targetRgb.length; i++) {
			targetRgb[i] = 0;
		}
		int centerX = width >> 1;
		int centerY = height >> 1;
		int distanceX = (width - (width * 100) / factor) >> 1;
		int startX = distanceX;
		int endX = width - distanceX;
		int distanceY = (height - (height * 100) / factor) >> 1;
		int startY = distanceY;
		int endY = height - distanceY;
		for (int y = startY; y < endY; y++) {
			for (int x = startX; x < endX; x++) {
				distanceX = centerX - x;
				int targetX = centerX - (distanceX * factor) / 100;
				if (targetX < 0 || targetX >= width) {
					continue;
				}
				distanceY = centerY - y;
				int targetY = centerY - (distanceY * factor) / 100;
				if (targetY < 0 || targetY >= height) {
					continue;
				}
				int sourceIndex = y * width + x;
				int targetIndex = targetY * width + targetX;
				targetRgb[targetIndex] = sourceRgb[sourceIndex];
			}
		}
	}

	/**
	 * Returns the one scaled Pixel for the given new height and width.
	 * 
	 * @param oldLength
	 *            length of the rgb data source.
	 * @param oldWidth
	 *            the old width of the rgb data.
	 * @param oldHeigth
	 *            the old height of the rgb data.
	 * @param newWidth
	 *            the new width of the rgb data.
	 * @param newHeigth
	 *            the new height of the rgb data.
	 * @param currentX
	 *            the x position of the pixel to be scaled.
	 * @param currentY
	 *            the y position of the pixel to be scaled.
	 * @return position of the scaled pixel in the old rgb data array.
	 */
	public static final int scaledPixel(int oldLength, int oldWidth, int oldHeigth, int newWidth, int newHeigth,
			int currentX, int currentY) {
		int targetArrayIndex;
		int verticalShrinkFactorPercent = ((newHeigth * 100) / oldHeigth);
		int horizontalScaleFactorPercent = ((newWidth * 100) / oldWidth);
		targetArrayIndex = ((currentX * 100) / horizontalScaleFactorPercent)
				+ (oldWidth * ((currentY * 100) / verticalShrinkFactorPercent));
		if (targetArrayIndex >= oldLength)
			targetArrayIndex = oldLength - 1;
		if (targetArrayIndex < 0)
			targetArrayIndex = 0;
		return targetArrayIndex;
	}

	/**
	 * Scales the rgb data and stores it into the given scaledRgbData array and
	 * adds an alpha semi transparency value at the same time.
	 * 
	 * @param opacity
	 *            the alpha value, 255 (0xFF) means fully opaque, 0 fully
	 *            transparent
	 * @param scaleFactor
	 *            the factor by which the rgb data should be magnified in
	 *            percent, e.g. 130
	 * @param width
	 *            the width of the rgbData and the scaledRgbData (scanline
	 *            width)
	 * @param height
	 *            the height of the rgbData and the scaledRgbData (scanline
	 *            width)
	 * @param rgbData
	 *            the source rgbData
	 * @param scaledRgbData
	 *            the target rgbData array, must have the same dimensions like
	 *            the given rgbData
	 * @throws ArithmeticException
	 *             when width of scaleFactor is 0
	 */
	public static void scaleWithAlpha(int opacity, int scaleFactor, int width, int height, int[] rgbData,
			int[] scaledRgbData) {
		opacity = (opacity << 24) | 0xFFFFFF;
		if (scaleFactor < 100) {
			int xStart = ((width * 100) - (width * scaleFactor)) / 200;
			int yStart = ((height * 100) - (height * scaleFactor)) / 200;
			// for (int y=yStart; y < height-yStart; y++){
			// for (int x=xStart; x < width-xStart; x++){
			for (int y = 0; y < height; y++) {
				for (int x = 0; x < width; x++) {
					int xTarget = (x * scaleFactor) / 100 + xStart;
					int yTarget = (y * scaleFactor) / 100 + yStart;
					scaledRgbData[(yTarget * width) + xTarget] = (rgbData[(y * width) + x] | 0xff000000) & opacity;
				}
			}
			return;
		}
		int yStart = ((height - height * 100 / scaleFactor) / 2) * width;
		int xStart = (width - width * 100 / scaleFactor) / 2;
		for (int y = 0; y < height; y++) {
			int c1 = y * width;
			int c2 = yStart + (y * 100 / scaleFactor) * width;
			for (int x = 0; x < width; x++) {
				scaledRgbData[c1 + x] = (rgbData[c2 + xStart + x * 100 / scaleFactor] | 0xff000000) & opacity;
			}
		}
	}

	/**
	 * Do simple scaling of an image
	 * 
	 * @param argbSrc
	 * @param width
	 * @param height
	 * @param newWidth
	 * @param newHeight
	 * @return
	 */
	public static int[] simpleScale(int[] argbSrc, int width, int height, int newWidth, int newHeight) {
		if (argbSrc == null) {
			throw new NullPointerException("argb array is null");
		}
		if (newHeight > 0 && newHeight > 0 && (!(width == newWidth && height == newHeight))) {
			final int PRECISION = 1 << 16;
			int srcWidth = width;
			int srcHeight = height;
			int xRatio = srcWidth * PRECISION / newWidth;
			int yRatio = srcHeight * PRECISION / newHeight;

			int xOffset = xRatio >> 1;
			int yOffset = yRatio >> 1;

			int[] destinationRGB = new int[newWidth * newHeight];
			for (int i = 0; i < newHeight; i++) {
				int srcY = yOffset / PRECISION;
				for (int j = 0; j < newWidth; j++) {
					int srcX = xOffset / PRECISION;
					destinationRGB[i * newWidth + j] = argbSrc[srcY * srcWidth + srcX];

					xOffset += xRatio;
				}

				xOffset = xRatio >> 1;
				yOffset += yRatio;
			}
			return destinationRGB;
		} else
			return argbSrc;
	}

	/**
	 * Stretches the rgb data horizontal to the given left and rigth stretch
	 * factor and returns the new rgb data array.
	 * 
	 * @param argbArray
	 *            the rgb data to be stretched.
	 * @param leftStrechFactor
	 *            the stretch factor of the left.
	 * @param rightStrechFactor
	 *            the stretch factor of the rigth.
	 * @param width
	 *            the source width of the rgb data.
	 * @param heigth
	 *            the source heigth of the rgb data.
	 * @return stretched rgb data array.
	 */
	public static final int[] stretchHorizontal(int[] argbArray, int leftStrechFactor, int rightStrechFactor,
			int width, int heigth) {
		int newHeigthLeft = (heigth * leftStrechFactor) / 100;
		int newHeigthRight = (heigth * rightStrechFactor) / 100;
		int procentualScalingWidth;
		int biggerHeigth;
		if (newHeigthLeft < newHeigthRight) {
			procentualScalingWidth = (newHeigthRight - newHeigthLeft) / width;
			biggerHeigth = newHeigthRight;
		} else {
			procentualScalingWidth = (newHeigthLeft - newHeigthRight) / width;
			biggerHeigth = newHeigthLeft;
		}
		int[] newArgbArray = new int[biggerHeigth * width];
		return stretchHorizontal(argbArray, newHeigthLeft, newHeigthRight, biggerHeigth, width, heigth,
				procentualScalingWidth, newArgbArray);
	}

	/**
	 * Stretches the rgb data horizontal to the given left and heigth width and
	 * returns the new rgb data array.
	 * 
	 * 
	 * @param argbArray
	 *            the rgb data to be stretched.
	 * @param newLeftHeigth
	 *            the new left heigth of the rgb data.
	 * @param newRigthHeigth
	 *            the new rigth heigth of the rgb data.
	 * @param biggerHeigth
	 *            the bigger heigth of left and rigth heigth.
	 * @param width
	 *            the source width of the rgb data.
	 * @param heigth
	 *            the source heigth of the rgb data.
	 * @param procentualScalingHeight
	 *            the procentual scaling heigth(biggerHeigth -
	 *            smallerSmaller)/widthOfTheOriginalImage).
	 * @param newArgbArray
	 *            the new rgb data where the changes getting in.
	 * @return return the filled newArgbArray with stretched changes.
	 */
	public static final int[] stretchHorizontal(int[] argbArray, int newLeftHeigth, int newRigthHeigth,
			int biggerHeigth, int width, int heigth, int procentualScalingHeight, int[] newArgbArray) {
		if (procentualScalingHeight == 0)
			procentualScalingHeight++;
		int length = newArgbArray.length;
		int oldLength = argbArray.length;
		// x and y position int the new array
		int idX = 0, idY = 0;
		// x and y position of the old array
		int x = 0, y = 0;
		// position in the new array
		int whereIamAt = 0;
		// Heighth for goal
		int newHeigth = newLeftHeigth;
		// start Heigth to goal
		int startColumn = (biggerHeigth - newHeigth) / 2;
		int endColumn = biggerHeigth - ((biggerHeigth - newHeigth) / 2);

		for (int i = 0; i < length; i++) {

			if (startColumn <= idY && endColumn >= idY) {
				newArgbArray[whereIamAt] = argbArray[scaledPixel(oldLength, width, heigth, width, newHeigth, x, y)];
				y = (y + 1) % newHeigth;
			} else {
				newArgbArray[whereIamAt] = 000000;
			}
			idY = (idY + 1) % (biggerHeigth);
			whereIamAt = idX + (idY * width);
			if (idY == 0) {
				idX++;
				x++;
				y = 0;
				if (newLeftHeigth < newRigthHeigth) {
					newHeigth += procentualScalingHeight;
				} else if (newLeftHeigth > newRigthHeigth) {
					newHeigth -= procentualScalingHeight;
				}
				startColumn = (biggerHeigth - newHeigth) / 2;
				endColumn = biggerHeigth - ((biggerHeigth - newHeigth) / 2);
			}
		}
		return newArgbArray;
	}

	/**
	 * Stretches the rgb data vertical to the given top and bottom stretch
	 * factor and returns the new rgb data array.
	 * 
	 * @param argbArray
	 *            the rgb data to be stretched.
	 * @param topStrechFactor
	 *            the stretch factor of the top.
	 * @param bottomStrechFactor
	 *            the stretch factor of the bottom.
	 * @param width
	 *            the source width of the rgb data.
	 * @param heigth
	 *            the source heigth of the rgb data.
	 * @return stretched rgb data array.
	 */
	public static final int[] stretchVertical(int[] argbArray, int topStrechFactor, int bottomStrechFactor, int width,
			int heigth) {
		int newWidthTop = (width * topStrechFactor) / 100;
		int newWidthBottom = (width * bottomStrechFactor) / 100;
		int procentualScalingHeight;
		int biggerWidth;
		if (newWidthTop < newWidthBottom) {
			procentualScalingHeight = (newWidthBottom - newWidthTop) / heigth;
			biggerWidth = newWidthBottom;
		} else {
			procentualScalingHeight = (newWidthTop - newWidthBottom) / heigth;
			biggerWidth = newWidthTop;
		}
		int[] newArgbArray = new int[biggerWidth * heigth];
		return stretchVertical(argbArray, newWidthTop, newWidthBottom, biggerWidth, width, heigth,
				procentualScalingHeight, newArgbArray);
	}

	/**
	 * Stretches the rgb data vertical to the given top and bottom width and
	 * returns the new rgb data array.
	 * 
	 * 
	 * @param argbArray
	 *            the rgb data to be stretched.
	 * @param newWidthTop
	 *            the new top width of the rgb data.
	 * @param newWidthBottom
	 *            the new bottom width of the rgb data.
	 * @param biggerWidth
	 *            the bigger width of top and bottom width.
	 * @param width
	 *            the source width of the rgb data.
	 * @param heigth
	 *            the source heigth of the rgb data.
	 * @param procentualScalingHeight
	 *            the procentual scaling height(biggerWidth -
	 *            smallerWidth)/heigthOfTheOriginalImage).
	 * @param newArgbArray
	 *            the new rgb data where the changes getting in.
	 * @return return filled the newArgbArray with stretched changes.
	 */
	public static final int[] stretchVertical(int[] argbArray, int newWidthTop, int newWidthBottom, int biggerWidth,
			int width, int heigth, int procentualScalingHeight, int[] newArgbArray) {
		if (procentualScalingHeight == 0)
			procentualScalingHeight++;
		int length = newArgbArray.length;
		int oldLength = argbArray.length;
		int insideCurrentY = 0;
		int insideCurrentX = 0, outsideCurrentX = 0;
		int sum1 = (biggerWidth - newWidthTop) / 2;
		int sum2 = biggerWidth - ((biggerWidth - newWidthTop) / 2);
		for (int i = 0; i < length; i++) {

			outsideCurrentX = (outsideCurrentX + 1) % biggerWidth;
			if (outsideCurrentX == 0) {
				if (newWidthTop < newWidthBottom) {
					newWidthTop += procentualScalingHeight;
					sum1 = (biggerWidth - newWidthTop) / 2;
					sum2 = biggerWidth - ((biggerWidth - newWidthTop) / 2);
				} else if (newWidthTop > newWidthBottom) {
					newWidthTop -= procentualScalingHeight;
					sum1 = (biggerWidth - newWidthTop) / 2;
					sum2 = biggerWidth - ((biggerWidth - newWidthTop) / 2);
				}
				insideCurrentY++;
				insideCurrentX = 0;
			}
			if (outsideCurrentX >= sum1 && outsideCurrentX < sum2) {
				insideCurrentX = (insideCurrentX + 1) % newWidthTop;
				newArgbArray[i] = argbArray[scaledPixel(oldLength, width, heigth, newWidthTop, heigth, insideCurrentX,
						insideCurrentY)];
			} else {
				newArgbArray[i] = 000000;
			}
		}
		return newArgbArray;
	}

	/**
	 * 
	 * @param image
	 *            to be processed
	 * @param desW
	 *            destination height
	 * @param desH
	 *            destination width
	 * @param isBackgroundTrans
	 *            set to true for alpha processing
	 * @param isTrans
	 *            set to false for alpha processing
	 * @return
	 * @throws NullPointerException
	 *             if image is null
	 */
	public final static Image zoomImage(Image image, int desW, int desH, boolean isBackgroundTrans, boolean isTrans) {
		if (image == null) {
			// return null;
			throw new NullPointerException("image is null");
		}
		Image desImg = null;
		int srcW = image.getWidth(); // source image width
		int srcH = image.getHeight(); // source image height
		int[] srcBuf = ImageUtil.getImageRGB(image, srcW, srcH);
		// compute interpolation table
		int[] tabY = new int[desH];
		int[] tabX = new int[desW];
		int sb = 0;
		int db = 0;
		int tems = 0;
		int temd = 0;
		int distance = srcH > desH ? srcH : desH;
		for (int i = 0; i <= distance; i++) { /* vertical direction */
			tabY[db] = sb;
			tems += srcH;
			temd += desH;
			if (tems > distance) {
				tems -= distance;
				sb++;
			}
			if (temd > distance) {
				temd -= distance;
				db++;
			}
		}
		sb = 0;
		db = 0;
		tems = 0;
		temd = 0;
		distance = srcW > desW ? srcW : desW;
		for (int i = 0; i <= distance; i++) { /* horizontal direction */
			tabX[db] = (short) sb;
			tems += srcW;
			temd += desW;
			if (tems > distance) {
				tems -= distance;
				sb++;
			}
			if (temd > distance) {
				temd -= distance;
				db++;
			}
		}
		// set transparence
		// if(isTrans){
		// int a= 100;//set the transparence of pixel 100
		// for(int i=0;i<srcBuf.length;i++){
		// if(srcBuf[i]==0x00FFFFFF)continue;
		// srcBuf[i]=(a<<24) | (srcBuf[i] & 0x00FFFFFF);// modify the highest 2
		// value
		// }
		// }

		// formation enlarge and shorten buffer pixel
		int[] desBuf = new int[desW * desH];
		int dx = 0;
		int dy = 0;
		int sy = 0;
		int oldy = -1;
		for (int i = 0; i < desH; i++) {
			if (oldy == tabY[i]) {
				System.arraycopy(desBuf, dy - desW, desBuf, dy, desW);
			} else {
				dx = 0;
				for (int j = 0; j < desW; j++) {
					desBuf[dy + dx] = srcBuf[sy + tabX[j]];
					dx++;
				}
				sy += (tabY[i] - oldy) * srcW;
			}
			oldy = tabY[i];
			dy += desW;
		}
		if (isTrans) {
			// int a= 100;//set the transparence of pixel 100
			for (int i = 0; i < desBuf.length; i++) {
				if (desBuf[i] == 0x00FFFFFF)
					continue;
				int alpha = ((desBuf[i] & 0xff000000) >>> 24) == 0 ? 0 : 100;
				desBuf[i] = ((alpha + 1) << 24) | (desBuf[i] & 0x00FFFFFF);// modify
				// the
				// highest
				// 2
				// value
			}
		}
		if (isBackgroundTrans) {
			desImg = javax.microedition.lcdui.Image.createRGBImage(desBuf, desW, desH, true);
		} else {
			desImg = javax.microedition.lcdui.Image.createRGBImage(desBuf, desW, desH, false);
		}

		return desImg;
	}

	private ImageScaling() {}

}
